#pragma once

/*
    封装一个SqliteHelper类，提供简单的数据库操作结构，以及数据的增删改查操作
    1.数据库文件的打开/创建
    2.针对打开的数据库进行操作
      表的操作
      数据的操作
    3.数据库的关闭
*/
#include "mq_logger.hpp"

#include <sqlite3.h>
#include <functional>
#include <iostream>
#include <vector>
#include <string>
#include <random>
#include <sstream>
#include <iomanip>
#include <atomic>
#include <cstring>
#include <fstream>
#include <sys/stat.h>

namespace mq
{

    class SqliteHelper
    {
    private:
        typedef int (*SqliteCallback)(void *, int, char **, char **);
        std::string _dbfile; // 数据库文件
        sqlite3 *_handler;   // 数据库操作句柄
    public:
        SqliteHelper(const std::string &dbfile) : _dbfile(dbfile), _handler(nullptr) {}

        // 打开/创建数据库文件
        bool open(int safe_leve = SQLITE_OPEN_FULLMUTEX)
        {
            // int sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs );
            int ret = sqlite3_open_v2(_dbfile.c_str(), &_handler, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | safe_leve, nullptr);
            if (ret != SQLITE_OK)
            {
                ELOG("创建/打开sqlite数据库失败: %s", sqlite3_errmsg(_handler));
                return false;
            }
            return true;
        }

        bool exec(const std::string &sql, SqliteCallback cb, void *arg)
        {
            int ret = sqlite3_exec(_handler, sql.c_str(), cb, arg, nullptr);
            if (ret != SQLITE_OK)
            {
                ELOG("%s 执行语句失败: %s", sql.c_str(), sqlite3_errmsg(_handler));
                return false;
            }
            return true;
        }

        void close()
        {
            if (_handler)
                sqlite3_close_v2(_handler);
        }
    };

    class StrHelper
    {
    public:
        static size_t split(const std::string &str, const std::string &sep, std::vector<std::string> *result)
        {
            size_t pos, idx = 0;
            while (idx < str.size())
            {
                pos = str.find(sep, idx);
                if (pos == std::string::npos)
                {
                    result->push_back(str.substr(idx));
                    return result->size();
                }

                if (pos == idx)
                {
                    idx = pos + sep.size();
                    continue;
                }
                result->push_back(str.substr(idx, pos - idx));
                idx = pos + sep.size();
            }

            return result->size();
        }
    };

    class UUIDHelper
    {
    public:
        static std::string uuid()
        {
            std::random_device rd;
            std::mt19937 generator(rd());
            std::uniform_int_distribution<int> distribution(0, 255);

            std::stringstream ss;
            // 将8个（0-255）数字变成16进制数字字符
            for (int i = 0; i < 8; i++)
            {
                ss << std::setw(2) << std::setfill('0') << std::hex << distribution(generator);
                if (i == 3 || i == 5 || i == 7)
                {
                    ss << '-';
                }
            }

            // 8字节数字序号
            static std::atomic<size_t> number(1);
            size_t num = number.fetch_add(1);

            for (int i = 7; i >= 0; i--)
            {
                //((num >> ((i * 8))) & 0xFF)每次取8位bit
                ss << std::setw(2) << std::setfill('0') << std::hex << ((num >> ((i * 8))) & 0xFF);
                if (i == 6)
                {
                    ss << '-';
                }
            }

            return ss.str();
        }
    };

    class FileHelper
    {
    private:
        std::string _filename;

    public:
        FileHelper(const std::string &filename) : _filename(filename) {}

        // 判断文件是否存在
        bool exists()
        {
            struct stat st;
            return (stat(_filename.c_str(), &st) == 0);
        }
        // 获取文件大小
        size_t size()
        {
            struct stat st;
            int ret = stat(_filename.c_str(), &st);
            if (ret < 0)
            {
                ELOG("获取文件大小失败 :%s", strerror(errno));
                return 0;
            }

            return st.st_size;
        }
        bool read(char *body, size_t offset, size_t len)
        {
            // 1. 打开文件
            std::ifstream ifs(_filename, std::ios::binary | std::ios::in);
            if (ifs.is_open() == false)
            {
                ELOG("%s 文件打开失败！", _filename.c_str());
                return false;
            }
            // 2. 跳转文件读写位置
            ifs.seekg(offset, std::ios::beg);
            // 3. 读取文件数据
            ifs.read(body, len);
            if (ifs.good() == false)
            {
                ELOG("%s 文件读取数据失败！！", _filename.c_str());
                ifs.close();
                return false;
            }
            // 4. 关闭文件
            ifs.close();
            return true;
        }
        bool read(std::string &body)
        {
            // 获取文件大小，根据文件大小调整body的空间
            size_t fsize = this->size();
            body.resize(fsize);
            return read(&body[0], 0, fsize);
        }
        bool write(const char *body, size_t offset, size_t len)
        {
            // 1. 打开文件
            std::fstream fs(_filename, std::ios::binary | std::ios::in | std::ios::out);
            if (fs.is_open() == false)
            {
                ELOG("%s 文件打开失败！", _filename.c_str());
                return false;
            }
            // 2. 跳转到文件指定位置
            fs.seekp(offset, std::ios::beg);
            // 3. 写入数据
            fs.write(body, len);
            if (fs.good() == false)
            {
                ELOG("%s 文件写入数据失败！！", _filename.c_str());
                fs.close();
                return false;
            }
            // 4. 关闭文件
            fs.close();
            return true;
        }
        bool write(const std::string &body)
        {
            return write(body.c_str(), 0, body.size());
        }

        // 更改文件名
        bool rename(const std::string &filename)
        {
            return (::rename(_filename.c_str(), filename.c_str()) == 0);
        }

        // 寻找父级目录
        static std::string parentDirectory(const std::string &filename)
        {
            auto pos = filename.find_last_of('/');
            if (pos == std::string::npos)
            {
                // test.txt 返回当前目录
                return "./";
            }

            const std::string &path = filename.substr(0, pos);
            return path;
        }

        // 创建文件
        static bool createFile(const std::string &filename)
        {
            std::fstream fs(filename, std::ios::binary | std::ios::out);
            if (!fs.is_open())
            {
                ELOG("%s ⽂件创建失败", filename.c_str());
                return false;
            }

            fs.close();
            return true;
        }
        // 删除文件
        static bool removeFile(const std::string &filename)
        {
            return (::remove(filename.c_str()) == 0);
        }

        // 创建目录 ./aaa/bbb/ccc
        static bool createDirectory(const std::string &path)
        {
            size_t pos, idx = 0;
            while (idx < path.size())
            {
                pos = path.find('/', idx);
                if (pos == std::string::npos)
                {
                    // 没找到/,有两种情况,一种是aaa,一种是aaa/bbb/ccc到最后的ccc了
                    return (::mkdir(path.c_str(), 0775) == 0);
                }

                const std::string &subpath = path.substr(0, pos);
                int ret = ::mkdir(subpath.c_str(), 0775);
                if (ret != 0 && errno != EEXIST)
                {
                    // EEXIST是目录以及存在了，目录已经存在不算错误，我们不能return退出
                    ELOG("创建 %s 目录失败 :%s", subpath.c_str(), strerror(errno));
                    return false;
                }
                idx = pos + 1;
            }

            return true;
        }

        // 删除目录
        static bool removeDirectory(const std::string &path)
        {
            const std::string &code = "rm -rf " + path;
            int ret = ::system(code.c_str());
            if (ret < 0)
            {
                ELOG("删除 %s 目录失败", path.c_str());
                return false;
            }

            return true;
        }
    };
}